package estates.persist.controller;

import java.util.List;
import estates.persist.exception.*;
import estates.util.Identifiable;
import javax.annotation.Resource;
import javax.transaction.UserTransaction;
import javax.persistence.*;

/**
 * Controller abstract class
 * @version 1.0
 * @author Marcin Chwedziak
 */
public class Controller<T extends Identifiable> {

  @Resource
  protected UserTransaction utx = null;
  @PersistenceUnit(unitName = "EstatesPU")
  protected EntityManagerFactory emf = null;
  protected Class<T> currentClass;

  public EntityManager getEntityManager() {
    if (emf == null) {
      emf = Persistence.createEntityManagerFactory("EstatesPU");
    }
    return emf.createEntityManager();
  }

  public Controller(Class<T> currentClass) {
    this.currentClass = currentClass;
  }

  public void create(T entity) throws RollbackFailureException, PreexistingEntityException, Exception {
    EntityManager em = null;

    try {
      utx.begin();
      em = getEntityManager();
      em.persist(entity);
      utx.commit();
    } catch (Exception ex) {
      try {
        utx.rollback();
      } catch (Exception re) {
        throw new RollbackFailureException("An error occurred attempting to roll back the transaction.", re);
      }
      if (find(entity.getId()) != null) {
        throw new PreexistingEntityException("Entity of type " + currentClass.getCanonicalName() + " with id = " + entity.getId().toString() + " is already persisted!");
      }
      throw ex;
    } finally {
      if (em != null) {
        em.close();
      }
    }
  }

  public void update(T entity) throws NonexistentEntityException, RollbackFailureException, Exception {
    EntityManager em = null;
    try {
      utx.begin();
      em = getEntityManager();
      entity = em.merge(entity);
      utx.commit();
    } catch (Exception ex) {
      try {
        utx.rollback();
      } catch (Exception re) {
        throw new RollbackFailureException("An error occurred attempting to roll back the transaction.", re);
      }
      String msg = ex.getLocalizedMessage();
      if (msg == null || msg.length() == 0) {
        Long id = entity.getId();
        if (find(id) == null) {
          throw new NonexistentEntityException("The entity of type " + currentClass.getCanonicalName() + " with id " + id + " no longer exists.");
        }
      }
      throw ex;
    } finally {
      if (em != null) {
        em.close();
      }
    }
  }

  public void destroy(Long id) throws NonexistentEntityException, RollbackFailureException, Exception {
    EntityManager em = null;
    try {
      utx.begin();
      em = getEntityManager();
      T entity;
      try {
        entity = em.getReference(currentClass, id);
        entity.getId();
      } catch (EntityNotFoundException enfe) {
        throw new NonexistentEntityException("The entity of type " + currentClass.getCanonicalName() + " with id " + id + " no longer exists.", enfe);
      }
      em.remove(entity);
      utx.commit();
    } catch (Exception ex) {
      try {
        utx.rollback();
      } catch (Exception re) {
        throw new RollbackFailureException("An error occurred attempting to roll back the transaction.", re);
      }
      throw ex;
    } finally {
      if (em != null) {
        em.close();
      }
    }
  }

  public List<T> list() {
    return list(true, -1, -1);
  }

  public List<T> list(int maxResults, int firstResult) {
    return list(false, maxResults, firstResult);
  }

  public List<T> list(boolean all, int maxResults, int firstResult) {
    EntityManager em = getEntityManager();
    try {
      Query q = em.createQuery("select object(o) from " + currentClass.getSimpleName() + " as o");
      if (!all) {
        q.setMaxResults(maxResults);
        q.setFirstResult(firstResult);
      }
      return q.getResultList();
    } finally {
      em.close();
    }
  }

  public T find(Long id) {
    EntityManager em = getEntityManager();
    try {
      return em.find(currentClass, id);
    } finally {
      em.close();
    }
  }

  public Long count() {
    EntityManager em = getEntityManager();
    try {
      return (Long) em.createQuery("select count(o) from " + currentClass.getSimpleName() + " as o").getSingleResult();
    } finally {
      em.close();
    }
  }
}
